"""
Mengji 模块加载器

MJ 的所有功能都是通过模块实现的，模块加载器引导模块加载，

MJ 使用位于运行目录下的 ``processors`` 和 ``vbus`` 目录中的 Python 文件作为扩展模块，
模块加载器扫描这两个目录中的 Python 文件作为目标模块，尝试调用每一个文件中的 ``load_mod`` 函数，
``load_mod`` 对于模块来讲是唯一的入口，模块加载行为完全自定义在 ``load_mod`` 函数中。

加载一个模块通常需要如下步骤，这些步骤可能不一定全部在 ``load_mod`` 中实现。

1. 打印调试信息。
2. 获取初始化参数。
3. 初始化成员，一个模块入口可以也往往初始化多个生成器派生类，对应更多节点。
4. 向载具注册对象，载具类负责管理模块间访问。
5. 向调度器注册，生命周期通过 ``ASche`` 管理。

常用的 ``load_mod`` 写法实现::

    def load_mod(veh: Vehicle):
        # 打印加载状态
        logger.debug("loading module...")
        # 获取关键参数
        sche, cfg = veh.get_v("sche", "config")
        # 初始化工作对象
        node = PrintNode()
        # 两次注册
        veh.reg_part(("label", node))
        sche.reg_generator(node)

``label`` 的选用：对于大部分工作类，标签通常直接通过直接在配置文件中指定的方式确定。
对于没有在配置文件指定的 wire 标签，通常使用两个端点加运算符的方式语义化命名。
当前 MJF 对多实例的情况支持得还不好，此类情况可能只能通过修改端点的方式暂时解决。

模块卸载，mengji 程序是可以重入的，模块必须能够正确卸载来满足内存管理要求，
对应要求每一个派生生成器都正确实现 ``_on_exit`` 方法。

date: 2025/3/20
author: SiHeng Tang
file: mod_loader.py
copyright(c) DFSA Eastern Software
此程序不能提供任何担保 WITHOUT WARRANTY OF ANY KIND
"""
import importlib
import logging
import os
import sys
from collections.abc import Mapping

from task_element import DataEndpoint

logger = logging.getLogger(__name__)


class ConfigHandler:
    """
    配置文件处理句柄，使用注册回调的方式实现配置文件解析
    MJ 配置文件解析器在解析配置文件时会先加载所有模块，然后扫描配置文件，
    模块加载器根据配置回调函数分别调用每个模块的配置加载回调函数。

    配置加载回调函数声明::

        def load_config_section(veh: Vehicle, cfg_section):
            pass
    """

    def __init__(self, name: str):
        """
        初始化配置处理句柄
        :param name: 模块 __name__ 字符串
        """
        logger.debug(f"initial config handler {name}")
        self._name = name
        self._cfg_map = {}

        setattr(sys.modules[name], "_mj_config_handler", self)

    # 这个函数主要是为了配合装饰器写法
    def add_handler(self, cfg_class: str):
        logger.debug(f"add config parsing handler with {cfg_class=}")

        def _handler(func):
            self._cfg_map.setdefault(cfg_class, func)
            return func

        return _handler

    def get_mapping(self):
        return self._cfg_map


class __InterfaceCollection:
    """
    收集函数的共有接口，抛弃 Python 风格以下划线开头的私有函数，防止出现意外访问。
    """

    def __init__(self, part_ref):
        # 接口保持对模块的引用，防止被意外回收
        self.__part = part_ref


def deep_clear(obj, _seen=None):
    """
    递归清空对象及内部成员的所有可写引用。
    适用场景：需要彻底断开引用链以便 gc 回收的复杂对象。
    只能用于正常优化无效的情况下，例如 pymavlink 的设计似乎本身就包含循环引用。
    """
    try:
        if _seen is None:
            _seen = set()

        obj_id = id(obj)
        if obj_id in _seen:
            return
        _seen.add(obj_id)

        # 1. 普通对象：清空 __dict__ 中的属性
        try:
            d = object.__getattribute__(obj, '__dict__')
        except AttributeError:
            pass
        else:
            for name, value in list(d.items()):
                try:
                    setattr(obj, name, None)
                except (AttributeError, TypeError):
                    # 只读属性、描述符等跳过
                    continue
                else:
                    deep_clear(value, _seen)

        # 2. 映射（dict / UserDict / 自定义映射）
        if isinstance(obj, Mapping):
            for k, v in list(obj.items()):
                setattr(obj, k, None)
                deep_clear(v, _seen)
                deep_clear(k, _seen)

        # 3. 列表 / 集合 / 元组（只清空可变容器）
        if isinstance(obj, list):
            for i, v in enumerate(obj):
                obj[i] = None
                deep_clear(v, _seen)
        elif isinstance(obj, set):
            obj.clear()
        elif isinstance(obj, tuple):
            # 元组不可变，只能递归其成员，无法修改
            for v in obj:
                deep_clear(v, _seen)
    except TypeError:
        pass

    # 4. 其他可迭代对象（避免 str/bytes 等）
    # elif isinstance(obj, Iterable) and not isinstance(obj, (str, bytes, bytearray)):
    #     if hasattr(obj, '__iter__') and hasattr(obj, '__getitem__'):
    #         try:
    #             # 类似 numpy 数组等，只读的不动
    #             pass
    #         except Exception:
    #             pass


def load_mods_from(veh, *args):
    """
    加载文件夹下的模块
    :param veh: 载具对象
    :param args: 模块路径，按顺序逐个加载
    """
    logger.debug(f"start module loading in directories: {args}")

    loaded_mods = []
    all_cfg_handler = {}

    # 枚举并加载目录下的所有模块
    for mod_dir in args:
        import_prefix = mod_dir.split("/")[1]

        mods = [it for it in os.walk(mod_dir)]
        rd, rds, mods = mods[0]

        for mod in mods:
            mod_name, _ = mod.split(".")
            mod_obj = importlib.import_module(f"{import_prefix}.{mod_name}")
            loaded_mods.append(mod_obj)

    # 确定模块正确设置配置加载回调句柄，整理并保存这些句柄
    for mod_obj in loaded_mods:
        if hasattr(mod_obj, "_mj_config_handler"):
            config_handlers: dict = getattr(mod_obj, "_mj_config_handler").get_mapping()
            # 并入配置文件解析回调函数字典
            logger.debug(f"{config_handlers=}")
            for key, func in config_handlers.items():
                if key in all_cfg_handler.keys():
                    all_cfg_handler[key].append(func)
                else:
                    all_cfg_handler.setdefault(key, [func, ])

    # 最后根据每个配置解析句柄解析配置文件
    cfg = veh.get_a("config")
    for config_section_name, handler in all_cfg_handler.items():
        if sections := cfg.get_a(config_section_name):
            for section in sections:
                for handler_func in handler:
                    handler_func(veh, section)

    logger.debug(f"loaded following modules: {loaded_mods=}")


def create_part_interface(extension_object):
    """
    创建一个部件的接口集合，用于组件间直接函数访问
    """
    public_interface = __InterfaceCollection(extension_object)
    attributes_list = dir(extension_object)

    for attribute_name in attributes_list:
        if not attribute_name.startswith('_'):
            attribute = getattr(extension_object, attribute_name)
            # 只给公共接口暴露函数和数据端点
            if callable(attribute) or (type(attribute) == DataEndpoint):
                setattr(public_interface, attribute_name, attribute)

    return public_interface
